/**
 * 时间戳加密工具
 */

// 最大的 ASCII 码
const MAX_ASCII = 123;
// 最小的 ASCII 码
const MIN_ASCII = 91;
// 中间 ASCII 码
const MID_ASCII = 96;

/**
 * string 转字节
 * @param str 字符串
 * @returns {*[]} 字节数组
 */
const stringToBytes = (str: string) => {
    let bytes: number[] = [];
    let len, c;
    len = str.length;
    for (let i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if (c >= 0x010000 && c <= 0x10FFFF) {
            bytes.push(((c >> 18) & 0x07) | 0xF0);
            bytes.push(((c >> 12) & 0x3F) | 0x80);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if (c >= 0x000800 && c <= 0x00FFFF) {
            bytes.push(((c >> 12) & 0x0F) | 0xE0);
            bytes.push(((c >> 6) & 0x3F) | 0x80);
            bytes.push((c & 0x3F) | 0x80);
        } else if (c >= 0x000080 && c <= 0x0007FF) {
            bytes.push(((c >> 6) & 0x1F) | 0xC0);
            bytes.push((c & 0x3F) | 0x80);
        } else {
            bytes.push(c & 0xFF);
        }
    }
    return bytes;
}
/**
 * 字节转 string
 * @param arr 字节数组
 * @returns {string} 字符串
 */
const byteToString = (arr: any[]) => {
    if (typeof arr === 'string') {
        return arr;
    }
    let str = '',
        _arr = arr;
    for (let i = 0; i < _arr.length; i++) {
        let one = _arr[i].toString(2),
            v = one.match(/^1+?(?=0)/);
        if (v && one.length == 8) {
            let bytesLength = v[0].length;
            let store = _arr[i].toString(2).slice(7 - bytesLength);
            for (let st = 1; st < bytesLength; st++) {
                store += _arr[st + i].toString(2).slice(2);
            }
            str += String.fromCharCode(parseInt(store, 2));
            i += bytesLength - 1;
        } else {
            str += String.fromCharCode(_arr[i]);
        }
    }
    return str;
}

/**
 * 根据时间戳对字符串进行加密
 *
 * <p>时间戳必须为字符串，在加解密的同时必须保持唯一。</p>
 * @param str 加密字符串
 * @param timestamp 加密时间戳
 */
const encrypt = (str: string, timestamp: string) => {
    if (timestamp) {
        timestamp = (timestamp + '').trim();
        let eng = /^[A-Za-z]+$/
        let newStr = '';
        let j = 0;
        for (let i = 0; i < str.length; i++) {
            if (j > timestamp.length - 1) {
                j = 0;
            }
            if (eng.test(str[i])) {
                let strBytes = stringToBytes(str[i]);
                let num = Number(strBytes[0]) + Number(timestamp[j]), enStr;
                // 由字母转成符号的话需要进行标记，标记为 (^tse,newChar,oldChar$)
                if ((num >= MIN_ASCII && num <= MID_ASCII) || num >= MAX_ASCII) {
                    enStr = `(^tse, ${byteToString([strBytes])}, ${num}$)`;
                } else {
                    enStr = byteToString([num]);
                }
                newStr += enStr;
                j++;
            } else {
                newStr += str[i];
            }
        }
        return window.btoa(encodeURIComponent(newStr));
    }
}

/**
 * 解密
 *
 * <p>解密的时间戳必须与加密时的时间戳相同</p>
 * @param ciphertext 加密文本
 * @param timestamp 加密时的时间戳
 */
const decrypt = (ciphertext: string, timestamp: string) => {
    if (timestamp) {
        timestamp = (timestamp + '').trim();
        let str = decodeURIComponent(window.atob(ciphertext));
        let eng = /^[A-Za-z]+$/;
        let newStr = '';
        let j = 0;
        for (let i = 0; i < str.length; i++) {
            if (j > timestamp.length - 1) {
                j = 0;
            }
            let marker;
            // 确保标记有足够长的字符组合 (^tse,
            if (str.length - i > 6) {
                marker = str[i] + str[i + 1] + str[i + 2] + str[i + 3] + str[i + 4] + str[i + 5];
            }
            // 检索到特殊标记, 标记为 (^tse,newChar,oldChar$)
            if (marker && marker === '(^tse,') {
                let k = i, end, markerStr = '', markerArr;
                while (end !== '$)') {
                    markerStr += str[k];
                    end = str[k] + str[k + 1];
                    k++;
                }
                markerArr = markerStr.replace('(', '').split(',');
                newStr += markerArr[1].trim();
                // 跳跃定位
                i = k;
                j++;
            } else if (eng.test(str[i])) {
                let strBytes = stringToBytes(str[i]);
                let num = Number(strBytes[0]) - Number(timestamp[j]);
                newStr += byteToString([num]);
                j++;
            } else {
                newStr += str[i];
            }
        }
        return newStr;
    }
}

/**
 * 获取当前时间戳
 */
const getCurrentTimestamp: () => string = () => {
    return new Date().getTime() + '';
}

export {encrypt, decrypt, getCurrentTimestamp};

